//1.程序的翻译环境和执行环境

//test.c（源文件）文件在经过一系列操作之后会生成一个test.exe的可执行文件    test.exe后续经过一些处理才会运行
//这个一系列过程：  编译 + 链接       编译由编译器处理    链接由链接器处理

//test.c——————>test.exe：这个过程中需要的环境叫翻译环境，在这环境中源代码被转换为可执行的机器指令。
//test.exe——————>运行成功：这个过程中需要的环境叫执行环境，用于实际执行代码

//test.exe本质上是一个二进制文件。      也就是说.c到.exe的过程就是将文本文件转换成一个二进制文件的过程

//#include<stdio.h>
//extern int add(int x,int y);
//int main(void)
//{
//    int arr[10]={0};
//    int i=0;
//    for(i=0;i<10;i++)
//    {
//        arr[i]=i;
//    }
//    for(i=0;i<10;i++)
//    {
//        printf("%d ",arr[i]);
//    }
//    printf("\n");
//    printf("%d",add(arr[1],arr[2]));
//    return 0;
//}



//每一个源文件 "编译器" 都会进行单独处理，编译后生成自己对应的目标文件（比如 .obj文件（win系统）     .o文件（ios系统））
//一个程序能够成功执行可能还需要引用其他源文件中的函数。而 "链接器" 会将这个函数能够执行用到的  目标文件+链接库  进行处理，生成可执行文件.exe

//汇总：（1）组成一个程序的每个源文件通过编译过程分别转行成目标文件（object code）
//（2）每个目标文件由连接器（linker）捆绑在一起，形成一个单一完整的可执行程序
//（3）链接器同时也会引入标准c函数库中任何被该程序用到的的函数，而且它可以搜索程序员个人的程序库，将其他需要的函数也链接到程序中
//c库函数就比如你的printf()函数，这都是在c库里面的源文件中





//编译本身也分为几个阶段
//编译的三阶段  预编译  编译  汇编       这三个是编译（可执行程序是经过编译和链接形成的）的细分      编译之后生成目标文件
// # 表明预处理器会在编译器之前处理这条指令  #include<stdio.h>是将stdio.h文件中所有的内容都复制到我们的代码文件中


//（1）预编译阶段：
//预编译后产生的文件是 .i        预处理后的文件会将 注释消掉， #define定义的符号替换，和include(包括)stdio.h的文件放在一起，当然包括的文件也会消掉注释替换符号     然后形成一个新的.i文件
//也就是说，预处理后产生的文件还是文本文件     

//#include头文件的包含，也就是将头文件中的内容放到了.c的文件当中去
//预编译中会将包含的头文件中的注释和自己写的注释删除           （一般是用空格来替换注释的内容）


//编译阶段：
//编译后产生的文件是 .s               编译的过程有，语法分析，词法分析，语义分析，符号汇总       （想深入学习看编译原理）         ---分析各种错误就是在编译阶段完成的
//符号汇总：都是一些函数名全局变量之类的
//编译的过程是将c语言的代码转化成汇编代码         也就是说这个文件中放的就是汇编代码


//汇编阶段：
//汇编后生成的文件是.o(.obj)文件，也就是目标文件  这种文件就是二进制文件了
//这个过程就是将汇编代码转换成了二进制代码（也叫二进制指令）
//此阶段会将上阶段的符号汇总 形成符号表      add 地址  这种形式    符号  地址


//链接：
//不管是主函数或者是被调用的函数，只要在程序中使用，都生成函数所在文件的目标文件   
//这个过程会  合并段表   符号表的合并和重定位
//会将多个.o文件通过链接器进行链接，最后生成main主函数所在的文件的.exe文件

//.o的文件是有相同的格式的，都是某个区间存放对应的数据     这种文件的格式是elf文件格式（类似.txt类型）











//2.运行环境（.exe文件的执行环境）
//（1）程序必须载入内存中，在有操作系统的环境中：一般这个由操作系统完成。在独立的环境中，程序的载入必须由手工安排，也可能是通过可执行代码置入只读内存来完成
//（2）程序执行便开始。接着便调用main函数
//（3）开始执行程序代码。这个时候程序将使用一个运行时堆栈（stack）,存储函数的局部变量和返回地址。程序同时也可以由静态内存（static），存储与静态内存中的变量在程序的整
//个执行过程一直保留它们的值
//（4）终止程序。正常终止main函数，也可能是意外终止  （阅读程序员的自我修养-链接装载与库）









//3.预处理详解（预编译）

//预定义符号：
//这些符号本身就存在不是自己定义的      #define max 100   像max这种是自己定义的并不是预定义符号
//#include<stdio.h>
//int main(void)
//{
//    printf("%s\n",__FILE__); //这个打印的就是主函数所在文件的绝对路径                      -----包括下面的内容，前后两个是两个_
//    printf("%d\n",__LINE__);//这个是当前代码所在的行数
//    printf("%s\n",__DATE__);//这个是今天的年月日
//    printf("%s\n",__TIME__);//这个是打印出这行代码的时间(精确到秒）          上面的这些东西可以记录到日志文件中去
//    printf("%s\n",__FUNCTION__ );//运行这行代码的时候返回所在的函数
//}
//类似的还有__STDC__  遵循c语言的标准的话，这个是符号是1








//4.预处理指令
//#开头的都叫预处理指令             在预处理阶段（预编译）处理的
//比如#define      #include          #pragma pack(4):设置默认对对齐数是4，括号里面不填数字就是恢复默认。一对的存在
//#if     #endif    #line     #ifdef

//#define作用：
//（1）可以定义标识符   #define max 1000        int a=max;     预编译后就是 int a=100;
//也可以定义字符串：#define str "hehe"     也可以节省名字  #define ft floot         ft b=3.14;
//用更形象的符号来替换一种实现 #define do_forever for(;;)
//在写case 语句的时候自动把break写上     #define CASE break; case
//如果定义的东西过长，可以分成几行写，除了最后一行外，每行的最后都加上一个反斜杠（续行符\）   #define DEBUG_PRINT printf("file:%s\tline:%d\t                                                                                                            data:%s\ttime:%s\n,\
//                                                                                     __FILE__,__LINE__,__DATE__,__TIME__)
//能替换变量，关键字，函数，甚至一段代码

//define 定义符号的时候后面尽量不要加;（分号），除非是需要的时候。因为这个替换会将后面的所有内容都替换掉    #define max 100;   max实际上是100;

//还有define定义的符号的值不要去改变   #define max 100    main( max=99;)这种就是错误的

//补充：如果将#define int* STR    STR a,b;     这样去定义，定义出来的a是一个int*  类型的变量，但是b只是一个int类型的变量。     
//这个和typedef去类型重定义是有区别的，typedef int* STR   STR a,b;   这样的情况，定义出来两个的变量都是指针类型的变量




//（2）可以定义宏
//#define机制包括了一个规定，允许把参数替换到文本中，这种实现方式通常称为宏（macro）或定义宏（define macro）

//宏的申明方式：
//#define name(parament-list)stuff其中的parament-list是由一个由逗号隔开的符号表，他们可能出现在stuff中
//parament-list其实就是参数，参数和参数之间用逗号隔开（如果拥有多个参数的情况下）

//注意：参数列表的左括号必须与name挨着。如果两者之间有空格存在，参数列表就会被解释为stuff的一部分
//#include<stdio.h>
//#define SQUARE(x) x*x
//int main(void)
//{
//    int ret=SQUARE(5);//也就是说SQUARE（x)这个宏因为没有参数还不能替换，直到有参数之后才替换为 x*x,在参与运算的时候只是被替换成x*x,并不会将x*x的值计算出来参与
//    //int ret=SQUARE(5+1);这样的形式就会出错，因为会替换成 5+1*5+1=11，宏是完完全全替换过去的       除非在宏的定义就是#define SQUARE(x) (x)*(x)
//
//    printf("%d",ret);
//}




//#define替换规则
//（1）在调用宏时，首先对参数进行检查，看看是否包含任何由#define定义的符号。如果有，他们首先被替换      例如：#define max 100    #define app max
//（2）替换文本随后被插入到程序中原来文本的位置。对于宏，参数名被他们的值替换
//（3）最后再次对结果文件进行扫描，看看它是否包含任何由#define定义的符号。如果是，就重复上述的整理过程

//注意：
//（1）宏参数和#define定义中可以出现其他#define定义的变量，但对于宏，不能出现递归
//（2）当预处理器搜索#define定义的符号的时候，字符串常量的内容并不被搜索            也就是说不能#define arr "abcdef"      char arr[]="mn arr kl";并不会发生替换
// 简单的来说就是不能将define定义的宏 放在""之中
//#include<stdio.h>
//#define max 100
//#define app max
//#define af "abcdef"
//int main(void)
//{
//    int a=max+app;
//    char arr[]=af;
//    printf("%d\n",a);
//    printf("%s\n",arr);
//    return 0;
//}







//5.# h和 ##     (如何把宏的参数插入到字符串中)

//#的作用：

//#include<stdio.h>
//#define PRINT(X) printf("the value of " #X "=%d\n",X)//请注意，里面吧字符串分开打印就可以实现了               尽量把宏写成大写
//void print(int a)
//{
//    printf("a=%d",a);
//}
//int main(void)
//{
//    int a=10;
//    int b=20;
////    printf("the value of a is %d\n",a);//可以将这句话封装为一个函数
////    print(a);//但是问题就来了，如果把b传进去输出的是a=20，明显不合理,然后可以这样解决
//    PRINT(a);
//    PRINT(b);//#X会被替换成"X",也就是printf("the value of" #X "=%d\n",X)，注意#X并不在 "" 里面       
//
//}
//注意：一个#只能和定义宏相搭配使用，在主函数中直接使用可能会出现问题




//##的作用：
//##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本段创还能标示符（合成一个符号的时候，看作为一个整体）

//#include<stdio.h>
//#define CAT(X,Y) X##Y
//int main(void)
//{
//    int class84=2019;
//    printf("%d\n",class84);//这样输出84没问题
//    printf("%d\n",CAT(class,84));//这样也会输出2019，也就是说##会将符号两边的字符串连成一个整体    也就是替换成printf("%d\n",class84)
//    return 0;
//}
//注意：这样的连接产生的必须是一个合法的标示符，否则其结果就是未定义的、









//6.带有副作用的宏参数

//副作用的参数：具有作用，但是在作用过后会遗留下一些相关的问题  a=10;b=++a;    a这个参数在给b复制以后发生了改变

//当宏参数在宏的定义中出现超过一次的时候，如果参数带有副作用，那么你在使用这个宏的时候就可能出现不可预测的后果。副作用就是表达式求值的时候出现的永久性的效果，比如：
//x+1:这种不带有副作用     x++:这种就有副作用
//MAX宏可以证明具有副作用的参数引起的问题
//#include<stdio.h>
//#define MAX(X,Y) (X)>(Y)?(X):(Y)
//int main(void)
//{
//    int a=10;
//    int b=11;
////    int max=MAX(a,b);//这种形式写出来的参数是没有问题的，没有副作用
//    int max=MAX(a++,b++);//替换之后就是
//    printf("%d\n",max);
//
//    printf("a=%d\nb=%d\n",a,b);
//    return 0;
//}









//7.宏和函数的对比

//宏的参数运算不会对类型有限制，如果要用函数的实现方式就得定义多个比较的函数。       但是无论哪种方式，用于接收结果的值还是得符合类型
//#include<stdio.h>
//#define MAX(X,Y) X>Y?X:Y
//int Max(int x,int y)
//{
//    return x,y?x:y;
//};
//int main(void)
//{
//    float max=MAX(3.14,6.28);//宏和类型无关，随便用各种类型比较。       也就是说对于需要操作不同类型的函数的可以使用宏
//    int b=Max(2,4);
//    printf("%d",max);
//}
//函数在调用的时候会有函数的调用和返回的开销    所以说简单的函数尽量用宏来实现（也就是调用和返回的时间可能比计算的时间还长）
//宏在预处理阶段就完成了替换，没有函数的调用和返回的开销


//宏的缺点：
//每次使用宏的时候，一份宏定义的代码插入到程序中，除非宏比较短，否则可能会大幅度增加代码的长度
//#define TEST() printf(".....") //假设这个省略号代表着非常多的内容，预编译以后，这个东西就会到代码程序中，直接暴增几十行

//宏是不能调试的  （一旦调试的时候就代表着进入了预编译阶段了，也就是我们调试的是.exe这个可执行程序）你看到的是未替换前的代码，但是调试的时候确实宏替换后的代码

//由于宏与类型无关，也就是不够严谨

//宏可能会带来符号优先级的问题，导致程序出现错误（也就是表达式的操作符和宏中的操作符优先加可能不一样），所以说用宏的时候多用括号
//还有宏带有副作用的参数可能会有副作用

//宏的牛逼之处：
//函数的定义无法传递类型，但是宏能够做到 #define SIZEOF(type) sizeof(type)
//碰见一些较长的定义可以替换一下  #define MALLOC(num,type) (type*)malloc(num*sizeof(type))          开辟空间的时候就可以这样用MALLOC(10,int)
//非常方便就对了


//命名约定：宏的名字用大写，函数的名字不要全部大写











//6.#undef
//#undef这个指令用于移除宏定义：
//#include<stdio.h>
//#define MAX 100
//int main(void)
//{
//    int a=MAX;
//#undef MAX//#undef的使用方式
//    int b=MAX;移除了之后再去进行赋值就报错，因为已经取消了这个宏的定义
//}









//7.命令行 定义
//许多C的编译器提供了一种能力，允许在命令行中定义符号。用于启动编译的过程。例如我们根据同一个源文件要编译出一个程序的不同版本的时候，这个特性就有点用处
//（假定某个程序中声明了一个某个长度的数组，如果机器内存有限，我么需要一个很小的数组，但是另外一个内存大写，我们需要一个数组能够大写）

//其实也是在预编译阶段进行替换









//8.条件编译
//在编译一个程序的时候，我们如果要将一条语句或者一组语句编译或者放弃是很方便的。因为我们有条件编译指令（选择性的编译）
//比如：调试性的代码，删除可惜保留碍事，所以我们可以选择性的编译
//#include<stdio.h>
//#define DEBUG 0//这个值多少都行，只要是定义过就可以
//int main(void)
//{
//    int arr[10]={1,2,3,4,5,6,7,8,9,0};
//    int i=0;
//    for(i=0;i<10;i++)
//    {
//        arr[i]=0;
//#ifdef DEBUG//如果DEBUG定义过，那么下面的这句话就参与编译。如果没有定义就不参与编译     这个条件也可是其他
//        printf("%d ",arr[i]);//这句话就是我想看看是不是真的赋值成功了，仅仅是迎来验证的，但是并不是我想实现这个程序需要的代码
//#endif//这两个是一对
//    }
//    return 0;
//}










//9.常见的条件编译指令：

//（1）#if 常量表达式                    比如常见的预处理器求值,这段代码如果为真，就将这段代码参与编译。可以是任何为真的语句  2>1都可以
//            ......
//    #endif

////例子:
//#include<stdio.h>
//
//int main(void)
//{
//    int arr[10]={1,2,3,4,5,6,7,8,9,0};
//    int i=0;
//    for(i=0;i<10;i++)
//    {
//        arr[i]=0;
//#if i==0//这个表达式只要是成立的就可以运行.注意是常量表达式，i是个变量，只看第一次的值。第一次的值如果不符合，后面不算数
//        printf("%d ",arr[i]);
//#endif
//    }
//    return 0;
//}


//（2）多个分支的条件编译    可以直接使用#if   #else  #endif   这样去使用
//#if 常量表达式
//......
//#elif 常量表达式
//......
//#elif 常量表达式
//......
//#else 常量表达式
//......
//#endif

//例子：
//#include<stdio.h>
//int main(void)
//{
//#if 8==1//就和if语句类似，有一个满足，下面的分支就不在进行判断，除非这一句是假的才会在下面进行判断
//    printf("1");
//#elif 2==0
//    printf("2");
//#else
//    printf("heheh");
//#endif
//    return 0;
//}



//（3）判断是否被定义
//#if defined(symbol)  //括号里面的是定义的符号      symbol指的是定义的MAX之类的符号     注意用的是括号
//上面这个等价于#ifdef symbol         当然结束的时候都是endif
//例子
//#include<stdio.h>
//#define MAX 100
//int main(void)
//{
//#if defined (MAX)    //或者说是这样#ifdef MAX       并且请注意这个是defined不是define,并且记得加括号
//    printf("hehe");
//#endif
//}


//#if !defined(symbol)                 -----这个的意思是没有定义过某个宏才会去编译
//#ifndef symbol这种写法还是和上面的是一样的       这两张写法的意思是没有定义过才会运行，定义过反而不会去运行了

//#include<stdio.h>//
//#define MAX 100
//int main(void)
//{
//#if !defined NUM
//    printf("没有定义过");
//#endif
//
//#ifndef MAX
//    printf("定义过这个符号");
//#endif
//    return 0;
//}



//（4）嵌套指令
//#include<stdio.h>
//#define OS_UNIX 5
//#define OPTION 2
//int main()
//{
//#if defined(OS_UNIX)//如果定义了这个符号
//#ifdef OPINION//在上面的判断成立的情况下，才会执行这一步判断
//    unix_version_option1();
//#endif
//#ifdef OPION2
//    unix_version_opton2();
//#endif
//#elif defined(OS_MSDOS)//这个是if  elif的形式，上面的形式成立了，那么这一句就不会在进行判断
//#ifdef OPTION2
//    msdos_verion_option2();
//#endif
//#endif
//}











//10.文件包含
//我们已经知。#include指令可以使另一个文件被编译，就像他实际出现于#include指令的地方一样
//这种替换方式很简单：预处理器先删除这条指令，并包含文件的内容替换。这样一个源文件被包含10次，那就被实际编译10次
//头文件的包含就是将被包含文件的内容拷贝一份过来

//如果包含的是本地文件，也就是自己创建的头文件用双引号""
//如果包含的是库函数的文件，就是尖括号 <>

//""查找策略：先在源文件的目录下查找，如果头文件未找到，编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。
//        linux环境的标准头文件的路径  /usr/include
//        win环境下的标准头文件的路径   c:Program Files(x86)\。。。。自己编译器\include
//可以说""引用范围大，耗费时间长
//#include"Add.h"
//#include"Add.h"
//int main()
//{
//    printf("%d ",add(1,2));
//    return 0;
//}
//避免重复，解决办法就是引用头文件中的函数在声明之前有判断指令










//11.其他预处理指令
//#error                  //遇见这个宏，这个宏后面放一些错误的信息，如果遇见这个宏就会将#error 后面的信息进行输出，然后整个程序就会停止运行
// 
//#pragma           用于在头文件中防止多重包含（头文件保护），确保只有第一次包含该头文件时有效。
// 
// 
//#line               这个宏的作用还是比较大的，能够变更__LINE__ 的数值，以及 __FILE__ 中的文件名
//#include <stdio.h>
//
//int main()
//{
//    printf("This code is on line %d, in file %s\n", __LINE__, __FILE__);
//#line 10             //不管前面是第几行，现在在这就是第10行
//    printf("This code is on line %d, in file %s\n", __LINE__, __FILE__);
//#line 20 "hello.cpp"    //别看上面就间隔了两行，现在这里就是第20行，下面的行数依次加一                而且从这里开始，这个文件的文件名就是hello.cpp
//    printf("This code is on line %d, in file %s\n", __LINE__, __FILE__);
//    printf("This code is on line %d, in file %s\n", __LINE__, __FILE__);
//}







//12.总结
//   #开头的这些指令都叫预处理指令，也就是在预编译阶段进行处理的
//C语言深度解剖这本书有时间看



//#include<stdio.h>
//#define max 100;//#define定义的标示符常量     注意：define 只是一个指令，并不是一个关键字
//#define ADD(X,Y) (X>Y? X:Y);//这是以宏的方式解决输出两数大小   注意：宏的参数并不用去强调类型      注意：如果只有单一的语句，直接写想要结果的表达式，不用带上判断关系
// //define定义的表示符号不占用内存，只是一个临时的符号，预编过后就没有了。
//
//
//int add(int x, int y)//这是以函数的方式解决两数之和
//{
//	int z = x + y;
//	return z;
//}
//int main()
//{
//	int a = 10, b = 20;
//	int c = 30, d = 40;
//	//下面两行输出a+b的值属于函数的方式
//	int sum = add(a, b);
//	printf("a+b=%d\n", sum);
//	//这下面的是以宏的方式进行运算
//	sum = ADD(c, d);
//	printf("c,d的最大值是=%d\n", sum);
//	return 0;
//}
//#include<stdio.h>
//#define add(x,y) (x+y);
//int main()
//{
//	int a = 20;
//	int b = 30;
//	int c = 10, d = 90;
//	int f;
//	printf("两个数字的和是：%d\n", a + b);
//	f = add(c, d);
//	printf("%d",f);
//	return 0;
//}


//补充：在链接阶段能发现函数的调用错误问题

//编译检测的是语法错误；语法出错才会报错，编译器检测不出来
//空指针，野指针是运行时错误



//补充： #ifdef #define #endif  主要的作用就是防止头文件的。



